package fr.pud.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import fr.pud.client.model.Address;
import fr.pud.client.model.PUDStation;
import fr.pud.client.model.Route;
import fr.pud.client.model.Time;
import fr.pud.client.model.Vehicle;

public class SolParser {
    private static class KeyPUDStation {
        private int        key;
        private PUDStation station;

        public KeyPUDStation(int key, PUDStation station) {
            this.key = key;
            this.station = station;
        }

        public int getKey() {
            return this.key;
        }

        public PUDStation getStation() {
            return this.station;
        }
    }

    public class Regex {
        public static final String ANYTHING                 = ".*";
        public static final String DIGIT                    = "\\d";
        public static final String NUMBER                   = Regex.DIGIT + "+";
        public static final String BRA_STRING               = "("
                                                                    + Regex.ANYTHING
                                                                    + ")";
        public static final String BRA_NUMBER               = "("
                                                                    + Regex.NUMBER
                                                                    + ")";
        public static final String PUDSTATIONS_OPEN         = "<nodes>";
        public static final String PUDSTATIONS_CLOSE        = "</nodes>";
        public static final String PUDSTATION_OPEN          = "<node>";
        public static final String PUDSTATION_CLOSE         = "</node>";
        public static final String ID_OPEN                  = "<id>";
        public static final String ID_CLOSE                 = "</id>";
        public static final String LOCATION_ID_OPEN         = "<location_id>";
        public static final String LOCATION_ID_CLOSE        = "</location_id>";
        public static final String OPENING_OPEN             = "<earliest>";
        public static final String OPENING_CLOSE            = "</earliest>";
        public static final String CLOSING_OPEN             = "<latest>";
        public static final String CLOSING_CLOSE            = "</latest>";
        public static final String LOADING_TIME_OPEN        = "<service_time>";
        public static final String LOADING_TIME_CLOSE       = "</service_time>";
        public static final String ROUTES_OPEN              = "<routes>";
        public static final String ROUTES_CLOSE             = "</routes>";
        public static final String ROUTE_OPEN               = "<route>";
        public static final String ROUTE_CLOSE              = "</route>";
        public static final String CAPACITY_OPEN            = "<capacite>";
        public static final String CAPACITY_CLOSE           = "</capacite>";
        public static final String MAX_DRIVE_DURATION_OPEN  = "<max_travel_time>";
        public static final String MAX_DRIVE_DURATION_CLOSE = "</max_travel_time>";
        public static final String STATIONS_OPEN            = "<stops>";
        public static final String STATIONS_CLOSE           = "</stops>";
        public static final String STATION_OPEN             = "<node_id>";
        public static final String STATION_CLOSE            = "</node_id>";
    }

    private static Address getAddress(int index, ArrayList<PUDStation> locations) {
        if (index >= 0 && index < locations.size()) {
            return locations.get(index).getAddress();
        }
        else {
            return null;
        }
    }

    private static Time getTime(int time) {
        return new Time(time / 3600, time % 3600 / 60);
    }

    public static ArrayList<Route> parse(String filename,
            ArrayList<PUDStation> locations) {
        String str = "";
        try {
            BufferedReader buffer = new BufferedReader(new FileReader(new File(
                    filename)));
            String line = null;
            while ((line = buffer.readLine()) != null) {
                str += line;
            }
            System.out.print("Parsing stations... ");
            HashMap<Integer, PUDStation> stations = SolParser.parseStations(
                    str, locations);
            if (stations != null) {
                System.out.println("success");
                System.out.println(stations.size() + " stations found.");
                System.out.println();
                System.out.print("Parsing routes... ");
                ArrayList<Route> routes = SolParser.parseRoutes(str, stations);
                if (routes != null) {
                    System.out.println("success");
                    System.out.println(routes.size() + " routes found.");
                    System.out.println();
                    return routes;
                }
                else {
                    System.out.println("fail");
                    System.out.println("No route found!");
                    return null;
                }
            }
            else {
                System.out.println("fail");
                System.out.println("No node found!");
                return null;
            }
        }
        catch (FileNotFoundException e) {
            System.out.println("Solution file " + filename + " not found!");
            return null;
        }
        catch (IOException e) {
            System.out.println("An error occured while reading solution file "
                    + filename);
            return null;
        }
    }

    private static Route parseRoute(String str,
            HashMap<Integer, PUDStation> stations) {
        Vehicle v;
        ArrayList<PUDStation> stops = new ArrayList<PUDStation>();
        int capacity;
        int maxDriveDuration;
        Matcher m;
        m = Pattern.compile(
                Regex.ANYTHING + Regex.CAPACITY_OPEN + Regex.BRA_NUMBER
                        + Regex.CAPACITY_CLOSE + Regex.ANYTHING).matcher(str);
        capacity = m.matches() ? Integer.parseInt(m.group(1)) : -1;
        m = Pattern.compile(
                Regex.ANYTHING + Regex.MAX_DRIVE_DURATION_OPEN
                        + Regex.BRA_NUMBER + Regex.MAX_DRIVE_DURATION_CLOSE
                        + Regex.ANYTHING).matcher(str);
        maxDriveDuration = m.matches() ? Integer.parseInt(m.group(1)) / 60 : -1;
        m = Pattern.compile(
                Regex.ANYTHING + Regex.STATIONS_OPEN + Regex.BRA_STRING
                        + Regex.STATIONS_CLOSE + Regex.ANYTHING).matcher(str);
        if (m.matches()) {
            String exp = m.group(1);
            Pattern p = Pattern.compile(Regex.BRA_STRING + Regex.STATION_OPEN
                    + Regex.BRA_STRING + Regex.STATION_CLOSE + Regex.ANYTHING);
            while ((m = p.matcher(exp)).matches()) {
                int id = Integer.parseInt(m.group(2));
                if (stations.containsKey(id)) {
                    stops.add(0, stations.get(id));
                }
                exp = m.group(1);
            }
        }
        if (capacity != -1 && maxDriveDuration != -1 && stops.size() >= 2) {
            v = new Vehicle(capacity, maxDriveDuration, stops.get(0),
                    stops.get(stops.size() - 1));
        }
        else {
            v = null;
        }
        if (v != null && stops.size() >= 2) {
            Route r = new Route();
            r.setVehicle(v);
            r.setStations(stops);
            return r;
        }
        else {
            return null;
        }
    }

    private static ArrayList<Route> parseRoutes(String str,
            HashMap<Integer, PUDStation> stations) {
        Matcher m = Pattern.compile(
                Regex.ANYTHING + Regex.ROUTES_OPEN + Regex.BRA_STRING
                        + Regex.ROUTES_CLOSE + Regex.ANYTHING).matcher(str);
        if (m.matches()) {
            ArrayList<Route> routes = new ArrayList<Route>();
            String exp = m.group(1);
            Pattern p = Pattern.compile(Regex.BRA_STRING + Regex.ROUTE_OPEN
                    + Regex.BRA_STRING + Regex.ROUTE_CLOSE + Regex.ANYTHING);
            while ((m = p.matcher(exp)).matches()) {
                Route r = SolParser.parseRoute(m.group(2), stations);
                if (r != null) {
                    routes.add(r);
                }
                exp = m.group(1);
            }
            return routes;
        }
        else {
            return null;
        }
    }

    private static KeyPUDStation parseStation(String str,
            ArrayList<PUDStation> locations) {
        int key;
        Address address;
        Time opening, closing;
        int loadingTime;
        Matcher m;
        m = Pattern.compile(
                Regex.ANYTHING + Regex.ID_OPEN + Regex.BRA_NUMBER
                        + Regex.ID_CLOSE + Regex.ANYTHING).matcher(str);
        key = m.matches() ? Integer.parseInt(m.group(1)) : -1;
        m = Pattern.compile(
                Regex.ANYTHING + Regex.LOCATION_ID_OPEN + Regex.BRA_STRING
                        + Regex.LOCATION_ID_CLOSE + Regex.ANYTHING)
                .matcher(str);
        address = m.matches() ? SolParser.getAddress(
                Integer.parseInt(m.group(1)), locations) : null;
        m = Pattern.compile(
                Regex.ANYTHING + Regex.OPENING_OPEN + Regex.BRA_NUMBER
                        + Regex.OPENING_CLOSE + Regex.ANYTHING).matcher(str);
        opening = m.matches() ? SolParser.getTime(Integer.parseInt(m.group(1)))
                : null;
        m = Pattern.compile(
                Regex.ANYTHING + Regex.CLOSING_OPEN + Regex.BRA_NUMBER
                        + Regex.CLOSING_CLOSE + Regex.ANYTHING).matcher(str);
        closing = m.matches() ? SolParser.getTime(Integer.parseInt(m.group(1)))
                : null;
        m = Pattern.compile(
                Regex.ANYTHING + Regex.LOADING_TIME_OPEN + Regex.BRA_NUMBER
                        + Regex.LOADING_TIME_CLOSE + Regex.ANYTHING).matcher(
                str);
        loadingTime = m.matches() ? Integer.parseInt(m.group(1)) / 60 : null;
        if (key != -1 && address != null && opening != null && closing != null
                && loadingTime != -1) {
            return new KeyPUDStation(key, new PUDStation(address, opening,
                    closing, loadingTime));
        }
        else {
            return null;
        }
    }

    private static HashMap<Integer, PUDStation> parseStations(String str,
            ArrayList<PUDStation> locations) {
        Matcher m = Pattern.compile(
                Regex.ANYTHING + Regex.PUDSTATIONS_OPEN + Regex.BRA_STRING
                        + Regex.PUDSTATIONS_CLOSE + Regex.ANYTHING)
                .matcher(str);
        if (m.matches()) {
            HashMap<Integer, PUDStation> stations = new HashMap<Integer, PUDStation>();
            String exp = m.group(1);
            Pattern p = Pattern.compile(Regex.BRA_STRING
                    + Regex.PUDSTATION_OPEN + Regex.BRA_STRING
                    + Regex.PUDSTATION_CLOSE + Regex.ANYTHING);
            while ((m = p.matcher(exp)).matches()) {
                KeyPUDStation key = SolParser.parseStation(m.group(2),
                        locations);
                if (key != null) {
                    stations.put(key.getKey(), key.getStation());
                }
                exp = m.group(1);
            }
            return stations;
        }
        else {
            return null;
        }
    }
}
